package com.puzzle;

import java.awt.Container;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Random;

import javax.imageio.ImageIO;

// 游戏
class Game implements PictureCtrl.Callback {
    // 回调
    public interface Callback {
        void onGameOver();  // 游戏结束事件
    }

    // 难度
    public enum Difficulty {
        EASY(9),    // 简单
        MEDIUM(25), // 中等
        HARD(36);   // 困难

        /**
         * 构造方法。
         * @param value 枚举值
         */
        Difficulty(int value) {
            _value = value;
        }

        /**
         * 获取枚举值。
         * @return 枚举值
         */
        public int value() {
            return _value;
        }

        private final int _value; // 枚举值
    }

    // 状态
    public enum Status {
        END,    // 已结束
        PLAYING // 游戏中
    }

    private final Callback mCallback;   // 回调
    private Difficulty mDifficulty;     // 难度
    private final Graphics mDrawer;     // 绘图者
    private Point mEmptyPoint;          // 空点，即没有图的那个点
    private final Container mGameBoard; // 游戏区域
    private final BufferedImage mImage; // 图片
    private boolean mPicHasBorder;      // 图片控件是否有边框标志
    private PictureCtrl[] mPictures;    // 图片控件数组
    private final Random mRandom;       // 随机数生成器
    private Status mStatus;             // 状态

    /**
     * 构造方法。
     * @param callback  回调
     * @param drawer    绘图者
     * @param gameBoard 游戏区域
     */
    public Game(Callback callback, Graphics drawer, Container gameBoard) {
        // 初始化成员
        mCallback = callback;
        mDifficulty = Difficulty.EASY;
        mDrawer = drawer;
        mGameBoard = gameBoard;
        mImage = new BufferedImage(Const.GAME_BOARD_WIDTH, Const.GAME_BOARD_HEIGHT, BufferedImage.TYPE_INT_RGB);
        mPicHasBorder = true;
        mRandom = new Random();
        mStatus = Status.END;

        // 设置图片
        this.setPicture(Const.PICTURE_FILE_PATH);
    }

    /**
     * 创建所有图片控件。
     */
    private void createPictures() {
        int left, top;                                              // 图片控件位置
        final int baseCount = (int)Math.sqrt(mDifficulty.value());  // 一行（列）的图片控件数
        final int size = Const.EMPTY_AREA_WIDTH / baseCount;        // 图片控件尺寸
        final Rectangle rect = new Rectangle();                                         // 图片控件位置、尺寸
        int j = 0;                                                  // 换行标志，0为第一行，1为第二行，依次类推

        // 删除所有图片控件
        this.deletePictures();

        // 创建所有图片控件
        mPictures = new PictureCtrl[Const.MAX_PICTURE_CTRL_COUNT];
        for (int i = 0; i < mDifficulty.value(); i++) {
            if (i % baseCount == 0 && i != 0) { // 如果一行满了，换到下一行
                j++;
            }

            // 计算左上角坐标
            left = Const.EMPTY_AREA_X + size * (i % baseCount);
            top = Const.EMPTY_AREA_Y + size * j;
            
            // 设置位置、尺寸
            rect.setBounds(left, top, size, size);

            // 创建图片控件
            mPictures[i] = new PictureCtrl(this, mPicHasBorder, mImage, mGameBoard, rect);
        }
    }

    /**
     * 删除所有图片控件。
     */
    private void deletePictures() {
        if (mPictures == null) {
            return;
        }

        for (PictureCtrl pictureCtrl: mPictures) {
            if (pictureCtrl == null) {
                break;
            }
            pictureCtrl.setVisible(false);
        }
        mGameBoard.removeAll();
    }

    /**
     * 游戏结束。
     */
    private void gameOver() {
        // 将状态设置为已结束
        mStatus = Status.END;
        
        // 发送通知
        mCallback.onGameOver();
    }

    /**
     * 获取游戏难度。
     * @return 游戏难度
     */
    public Difficulty getDifficulty() {
        return mDifficulty;
    }

    /**
     * 获取游戏状态。
     * @return 游戏状态
     */
    public Status getStatus() {
        return mStatus;
    }

    /**
     * 判断是否胜利。
     * @return 胜利则返回 true，否则返回 false
     */
    private boolean isWon() {
        boolean result = false;
        for (int i = 0; i < mDifficulty.value(); i++) {
            result = mPictures[i].isOnOriginalPosition();
            if (!result) {
                break;
            }
        }
        return result;
    }

    /**
     * 移动图片控件。
     * @param pictureCtrl   图片控件
     */
    @Override
    public void movePicture(PictureCtrl pictureCtrl) {
        if (mStatus != Status.PLAYING) {
            return;
        }

        final Point position = new Point(pictureCtrl.getLocation());

        // 如果要移动的图片控件与空点相邻，则移动
        if (position.y == mEmptyPoint.y && Math.abs(position.x - mEmptyPoint.x) == pictureCtrl.getWidth() || 
            position.x == mEmptyPoint.x && Math.abs(position.y - mEmptyPoint.y) == pictureCtrl.getHeight()) {
            pictureCtrl.setLocation(mEmptyPoint);
            mEmptyPoint = position;

            // 判断是否胜利
            if (this.isWon()) {
                this.gameOver();
            }
        }
    }

    /**
     * 获取图片控件是否有边框。
     * @return 有边框则为 true，否则为 false
     */
    public boolean pictureHasBorder() {
        return mPicHasBorder;
    }

    /**
     * 设置游戏难度。
     * @param diff  游戏难度
     */
    public void setDifficulty(Difficulty diff) {
        // 设置难度
        mDifficulty = diff;

        // 重新创建所有图片
        this.createPictures();

        // 将状态设置为已结束
        mStatus = Status.END;
    }

    /**
     * 设置图片。
     * @param filePath  图片文件路径
     */
    public void setPicture(String filePath) {
        final File file = new File(filePath);
        this.setPicture(file);
    }

    /**
     * 设置图片。
     * @param imageFile 图片文件
     */
    public void setPicture(File imageFile) {
        // 将图片画到游戏区域上
        try {
            final BufferedImage image = ImageIO.read(imageFile);
            mImage.getGraphics().drawImage(image, 0, 0, Const.GAME_BOARD_WIDTH, 
                Const.GAME_BOARD_HEIGHT, 0, 0, image.getWidth(), image.getHeight(), null);
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }
        mDrawer.drawImage(mImage, 0, 0, Const.GAME_BOARD_WIDTH, Const.GAME_BOARD_HEIGHT, null);

        // 画中间的空白区域
        mDrawer.setColor(Const.EMPTY_AREA_COLOR);
        mDrawer.fillRect(Const.EMPTY_AREA_X, Const.EMPTY_AREA_Y, Const.EMPTY_AREA_WIDTH, Const.EMPTY_AREA_HEIGHT);
        
        // 通知游戏区域重绘
        mGameBoard.repaint();

        // 创建所有图片
        this.createPictures();

        // 将状态设置为已结束
        mStatus = Status.END;
    }

    /**
     * 设置图片控件是否有边框。
     * @param hasBorder 有边框则为 true，否则为 false
     */
    public void setPictureHasBorder(boolean hasBorder) {
        mPicHasBorder = hasBorder;
        
        // 设置所有图片控件的边框
        for (int i = 0; i < mDifficulty.value(); i++) {
            mPictures[i].setBorder(hasBorder);
        }
    }

    /**
     * 游戏开始。
     */
    public void start() {
        // 重新创建所有图片
        if (mStatus != Status.END) {
            this.createPictures();
        }

        // 重置成员变量
        final int baseCount = (int)Math.sqrt(mDifficulty.value());
        final int size = mPictures[0].getWidth();
        mEmptyPoint = new Point(Const.EMPTY_AREA_X + size * baseCount, Const.EMPTY_AREA_Y + size * (baseCount - 1));
        mStatus = Status.PLAYING;

        // 右下角图片控件右移
        final Point position = new Point(mPictures[mDifficulty.value() - 1].getLocation());
        mPictures[mDifficulty.value() - 1].setLocation(mEmptyPoint);
        mEmptyPoint = position;

        // 乱序其它图片控件的位置
        int index1, index2;
        for (int i = 0; i < mDifficulty.value() - 1; i++) {
            do {
                index1 = mRandom.nextInt(mDifficulty.value() - 1);
                index2 = mRandom.nextInt(mDifficulty.value() - 1);
            } while (index1 == index2);
            this.swapPictures(index1, index2);
        }
    }

    /**
     * 交换两个图片控件的位置。
     * @param index1    图片控件1的索引
     * @param index2    图片控件2的索引
     */
    private void swapPictures(int index1, int index2) {
        final Point position1 = new Point(mPictures[index1].getLocation());
        final Point position2 = new Point(mPictures[index2].getLocation());
        mPictures[index1].setLocation(position2);
        mPictures[index2].setLocation(position1);
    }
}
